package project.messagestack.network.pop3;

import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;

import android.content.ContentValues;

import project.messagestack.network.ConnectionHandler;
import project.messagestack.network.MailTransport;
import project.messagestack.network.Transporter;
import project.messagestack.network.mail.Message;
import project.messagestack.network.mail.Message.msgType;
import project.messagestack.utils.DatabaseManager;
import project.messagestack.utils.Logger;

/**
 * 
 * @author Bartek Przybylski
 *
 */
public class POP3 implements ConnectionHandler {
  private int lastUpdateMsgCount;

  private DatabaseManager mdb;
  public POP3(DatabaseManager mdb) {
    this.mdb = mdb;
    mMsgCount = -1;
    lastUpdateMsgCount=0;
  }
  public final static int DEFAULT_POP3_PORT = 110;
  
  public final static int NO_ERROR = 444;
  public final static int ERROR_UNKNOWN_HOST = 454;
  public final static int ERROR_IO_ERROR = 464;
  
  private final static String SERVER_OK_RESP = "+OK";
  private final static String SERVER_ERR_RESP = "-ERR";
  private final static String POP3_LAST_LINE = "\n.\n";
  private int connect(String hostname, int port, boolean usessl) {
    try {
      mConnection = new MailTransport();
      mConnection.setUri(new URI("pop3", hostname, null, null), port);
      mConnection.setSecurity(usessl);
      mConnection.open();
      if (!mConnection.readLine().startsWith(SERVER_OK_RESP+" POP3")) {
        return ERROR_IO_ERROR;
      }
      Logger.logInfo("Connection established to " + hostname);
    } catch (IOException e) {
      Logger.logInfo(e.getMessage());
      return ERROR_IO_ERROR;
    } catch (URISyntaxException e) {
      Logger.logInfo(e.getMessage());
      return ERROR_IO_ERROR;
    }
    return NO_ERROR;
  }
  
  private void disconnect() {
    if (isConnected()) {
      mConnection.close();
      mMsgCount = -1;
      mHandlerConfiguration.clear();
      Logger.logInfo("Connection closed to " + mConnection.getHost());
    }
  }
  
  private boolean login(String user, String password) throws IOException {
    if (!isConnected()) {
      Logger.logWarn("Trying to login on not connected socket");
      return false;
    }
    String hostname = mConnection.getHost();
    String servResp;

    mConnection.writeLine("USER " + user);
    servResp = mConnection.readLine();
    if (servResp == null || servResp.startsWith(SERVER_ERR_RESP)) {
      Logger.logError(hostname + ": server error while logging (USER)");
      return false;
    }
    mConnection.writeLine("PASS " + password);
    servResp = mConnection.readLine();
    if (servResp == null) {
      Logger.logError(hostname + ": server error while logging (PASS)");
      return false;
    }
    if (servResp.startsWith(SERVER_ERR_RESP)) {
      Logger.logError("Login failed to " + hostname);
      return false;
    }
    Logger.logInfo("Login successful");
    return true;
  }

  private void updateMessageCount() throws IOException {
    fetchMsgCount();
  }
  
  private boolean isConnected() {
    return mConnection.isOpen();
  }
  
  private void fetchMsgCount() throws IOException {
    if (!isConnected()) {
      Logger.logInfo("Trying to fetching msg count on closed socket");
      return;
    }
    mConnection.writeLine("STAT");
    String servResp = mConnection.readLine();
    if (!servResp.startsWith(SERVER_OK_RESP)) {
      try {
        mMsgCount = Integer.parseInt(servResp.split(" ")[1]);
      } catch (NumberFormatException e) {
        Logger.logError("Invalid format number of message count");
        mMsgCount = -1;
      } catch (ArrayIndexOutOfBoundsException e) {
        Logger.logError("Wrong format of response from POP3 server");
        mMsgCount = -1;
      }
    }
  }

  public Message readMessage(int msgNum) throws IOException {
    if (msgNum > msgNum && msgNum < 1 && !isConnected()) {
      return null;
    }
    String result = "";
    mConnection.writeLine("RETR " + msgNum);
    do {
      String s = mConnection.readLine();
      result += s+"\n";
    } while (!result.endsWith(POP3_LAST_LINE));
    return new Message(result,msgType.MAIL);
  }
  
  // Methods inherited from implemented interface

  public void prepareConnection(ContentValues pList) {
    if (!pList.containsKey("username") ||
        !pList.containsKey("password") ||
        !pList.containsKey("hostname") ||
        !pList.containsKey("usessl")   ||
        !pList.containsKey("port")) {
      return;
    }
    String username = pList.getAsString("username");
    String password = pList.getAsString("password");
    String hostname = pList.getAsString("hostname");
    boolean usessl  = pList.getAsBoolean("usessl");
    int port = pList.getAsInteger("port");
    mHandlerConfiguration = pList;
    if (username != null && password != null && hostname != null && port != 0) {
      try {
        if (connect(hostname, port, usessl) != NO_ERROR) {
          return;
        }
        if (!login(username, password)) {
          return;
        }
        updateMessageCount();
      } catch (IOException e) { }
    } else {
      Logger.logError("Wrong arguments given for POP3 service");
    }
  }

  public void updateConfiguration(ContentValues delta) {
    boolean reconnectionNeeded = false;
    String[] availProps = {"hostname", "port", "username", "password", "usessl"};
    
    for (int i = 0; i < availProps.length; i++) {
      if (delta.containsKey(availProps[i])) {
        mHandlerConfiguration.remove(availProps[i]);
        mHandlerConfiguration.put(availProps[i],
                                  delta.getAsString(availProps[i]));
        reconnectionNeeded = true;
      }
    }
    
    if (reconnectionNeeded) {
      try {
        disconnect();
        connect(mHandlerConfiguration.getAsString("host"),
                mHandlerConfiguration.getAsInteger("port"),
                mHandlerConfiguration.getAsBoolean("usessl"));
        login(mHandlerConfiguration.getAsString("username"),
              mHandlerConfiguration.getAsString("password"));
        updateMessageCount();
      } catch (IOException e) { }
    }
  }
  
  public void stopConnection() {
    disconnect();
  }

  public void updateData() {
    try {
      this.updateMessageCount();
      Message msg = null;
      for ( ;lastUpdateMsgCount < mMsgCount + 1; lastUpdateMsgCount++) {
        msg = readMessage(lastUpdateMsgCount);
        if (msg != null)
          mdb.addNewMessage(msg);
      }
    } catch (IOException e) {
      lastUpdateMsgCount=0;
      Logger.logError("Error while updating message in POP3");
    }
  }
  
  public boolean isReady() {
    return isConnected() && mMsgCount >= 0;
  }
  
  protected Transporter mConnection;
  protected int mMsgCount;
  protected ContentValues mHandlerConfiguration;
  @Override
  public ContentValues getHandlerConfiguration() {
    return mHandlerConfiguration;
  }
}
